/*
 * Copyright (c) 2025 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
import { RuleListUtil } from "../../utils/common/DefectsList";
import { ArkFile, ts } from "arkanalyzer/lib";
import Logger, { LOG_MODULE_TYPE } from 'arkanalyzer/lib/utils/logger';
import { BaseChecker, BaseMetaData } from "../BaseChecker";
import { Defects } from "../../model/Defects";
import { FileMatcher, MatcherCallback, MatcherTypes } from "../../matcher/Matchers";
import { AstTreeUtils } from "arkanalyzer";
import { Rule } from "../../model/Rule";
import { IssueReport } from '../../model/Defects';
const logger = Logger.getLogger(LOG_MODULE_TYPE.HOMECHECK, 'PreferForOfCheck');
const gMetaData: BaseMetaData = {
    severity: 1,
    ruleDocPath: "docs/prefer-for-of-check.md",
    description: "Enforce the use of `for-of` loop over the standard `for` loop where possible",
};

interface ForOfCheckResult {
    line: number;
    character: number;
    endCol: number;  // 新增结束列字段
    message: string;
}

export class PreferForOfCheck implements BaseChecker {
    private defaultOptions: any[] = [];
    readonly metaData: BaseMetaData = gMetaData;
    public defects: Defects[] = [];
    public issues: IssueReport[] = [];
    public rule: Rule;
    private fileMatcher: FileMatcher = {
        matcherType: MatcherTypes.FILE,
    };

    public registerMatchers(): MatcherCallback[] {
        const matchfileBuildCb: MatcherCallback = {
            matcher: this.fileMatcher,
            callback: this.check
        }
        return [matchfileBuildCb];
    }
    private typeChecker!: ts.TypeChecker
    public check = (targetField: ArkFile) => {
        let code = targetField.getCode()?.toString() ?? '';
        const program = ts.createProgram([targetField.getFilePath()], {});
        this.typeChecker = program.getTypeChecker();
        const sourceFile = AstTreeUtils.getASTNode(targetField.getName(), targetField.getCode());
        const results = this.checkForOfUsage(sourceFile);
        results.forEach(result => {
            this.addIssueReport(result.line, result.character, result.endCol,targetField.getFilePath(), result.message);
        })
    }

    private checkForOfUsage(sourceFile: ts.SourceFile): ForOfCheckResult[] {
        const results: ForOfCheckResult[] = [];
        function isSingleVariableDeclaration(node: ts.Node): node is ts.VariableDeclarationList {
            return (
                ts.isVariableDeclarationList(node) &&
                node.declarations.length === 1 &&
                ts.isIdentifier(node.declarations[0].name)
            );
        }

        function isZeroInitialized(node: ts.VariableDeclaration): boolean {
            return (
                node.initializer !== undefined &&
                ts.isNumericLiteral(node.initializer) &&
                node.initializer.text === '0'
            );
        }

        function isLessThanLengthExpression(node: ts.Expression, indexName: string): ts.Expression | null {
            if (
                ts.isBinaryExpression(node) &&
                node.operatorToken.kind === ts.SyntaxKind.LessThanToken &&
                ts.isIdentifier(node.left) &&
                node.left.text === indexName &&
                ts.isPropertyAccessExpression(node.right) &&
                ts.isIdentifier(node.right.name) &&
                node.right.name.text === 'length'
            ) {
                return node.right.expression;
            }
            return null;
        }

        function isIncrement(node: ts.Expression, indexName: string): boolean {
            if (ts.isPrefixUnaryExpression(node) || ts.isPostfixUnaryExpression(node)) {
                return (
                    node.operator === ts.SyntaxKind.PlusPlusToken &&
                    ts.isIdentifier(node.operand) &&
                    node.operand.text === indexName
                );
            }

            if (ts.isBinaryExpression(node)) {
                if (
                    node.operatorToken.kind === ts.SyntaxKind.PlusEqualsToken || // x += 1
                    node.operatorToken.kind === ts.SyntaxKind.EqualsToken // x = x + 1 或 x = 1 + x
                ) {
                    if (
                        ts.isIdentifier(node.left) &&
                        node.left.text === indexName &&
                        ts.isNumericLiteral(node.right) &&
                        node.right.text === '1'
                    ) {
                        return true;
                    }

                    if (ts.isBinaryExpression(node.right)) {
                        const { left, right, operatorToken } = node.right;
                        return (
                            (ts.isIdentifier(left) && left.text === indexName && ts.isNumericLiteral(right) && right.text === '1' && operatorToken.kind === ts.SyntaxKind.PlusToken) || // x = x + 1
                            (ts.isNumericLiteral(left) && left.text === '1' && ts.isIdentifier(right) && right.text === indexName && operatorToken.kind === ts.SyntaxKind.PlusToken) // x = 1 + x
                        );
                    }
                }
            }
            return false;
        }

        function isIndexOnlyUsedWithArray(
            body: ts.Statement,
            indexName: string,
            arrayExpression: ts.Expression
        ): boolean {
            const arrayText = arrayExpression.getText(sourceFile);
            let indexUsedOutsideArrayAccess = false;

            function checkNode(node: ts.Node, outerScope: Set<string>): void {
                if (ts.isVariableDeclarationList(node)) {
                    for (const declaration of node.declarations) {
                        if (ts.isIdentifier(declaration.name) && declaration.name.text === indexName) {
                            outerScope.add(indexName);
                        }
                    }
                }
                if (ts.isElementAccessExpression(node)) {
                    if (ts.isBinaryExpression(node.parent) && node.parent.operatorToken.kind === ts.SyntaxKind.EqualsToken) {
                        // 解构赋值的情况，跳过
                        return;
                    }
                    const objectText = node.expression.getText(sourceFile);
                    if (
                        objectText === arrayText &&
                        ts.isIdentifier(node.argumentExpression) &&
                        node.argumentExpression.text === indexName
                    ) {
                        return; // 索引变量被用于数组访问，跳过
                    }
                }

                if (ts.isBinaryExpression(node) && node.operatorToken.kind === ts.SyntaxKind.EqualsToken) {
                    if (ts.isElementAccessExpression(node.left)) {
                        const objectText = node.left.expression.getText(sourceFile);
                        if (
                            objectText === arrayText &&
                            ts.isIdentifier(node.left.argumentExpression) &&
                            node.left.argumentExpression.text === indexName
                        ) {
                            indexUsedOutsideArrayAccess = true; // 索引变量被用于修改数组元素
                            return;
                        }
                    }
                }

                if (ts.isIdentifier(node) && node.text === indexName) {
                    if (!outerScope.has(indexName)) {
                        indexUsedOutsideArrayAccess = true; // 索引变量被用于非数组访问的场景
                    }
                }
                const parentScope = new Set(outerScope);
                ts.forEachChild(node, (childNode) => checkNode(childNode, parentScope));
            }
            checkNode(body, new Set());
            return !indexUsedOutsideArrayAccess;
        }

        function checkForStatement(node: ts.ForStatement): void {
            if (
                node.initializer !== undefined &&
                ts.isVariableDeclarationList(node.initializer) &&
                isSingleVariableDeclaration(node.initializer) &&
                isZeroInitialized(node.initializer.declarations[0])
            ) {
                const name = node.initializer.declarations[0].name;
                if (ts.isIdentifier(name)) {
                    const indexName = name.text;
                    if (node.condition !== undefined) {
                        const arrayExpression = isLessThanLengthExpression(node.condition, indexName);
                        if (arrayExpression && node.incrementor !== undefined && isIncrement(node.incrementor, indexName)) {
                            if (isIndexOnlyUsedWithArray(node.statement, indexName, arrayExpression)) {
                                const startPos = node.getStart(sourceFile, true);
                                const endPos = node.getEnd();  // 获取for语句结束位置
                                const { line, character } = ts.getLineAndCharacterOfPosition(sourceFile, startPos);
                                const { character: endChar } = ts.getLineAndCharacterOfPosition(sourceFile, endPos);
                                results.push({
                                    line: line + 1,
                                    character: character + 1,
                                    endCol: endChar + 1,  // 添加结束列
                                    message: 'Expected a `for-of` loop instead of a `for` loop with this simple iteration.'
                                });
                            }
                        }
                    }
                }
            }
        }

        function visitNode(node: ts.Node): void {
            if (ts.isForStatement(node)) {
                checkForStatement(node);
            }
            ts.forEachChild(node, visitNode);
        }
        visitNode(sourceFile);
        return results;
    }
    private async addIssueReport(line: number, startCol: number, endCol:number,filePath: string, message: string) {
        const severity = this.rule.alert ?? this.metaData.severity;
        const description = message;
        const defect = new Defects(line, startCol, endCol, description, severity,
            this.rule.ruleId, filePath, this.metaData.ruleDocPath, true, false, false);
        this.issues.push(new IssueReport(defect, undefined))
        RuleListUtil.push(defect);
    }
}